/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.design.widget;

import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.swipeUp;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.annotation.TargetApi;
import android.graphics.Rect;
import android.support.design.testapp.CoordinatorLayoutActivity;
import android.support.design.testapp.R;
import android.support.design.testutils.CoordinatorLayoutUtils.DependentBehavior;
import android.support.design.widget.CoordinatorLayout.Behavior;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.ImageView;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class CoordinatorLayoutTest {

  @Rule
  public final ActivityTestRule<CoordinatorLayoutActivity> activityTestRule =
      new ActivityTestRule<>(CoordinatorLayoutActivity.class);

  @Test
  @TargetApi(21)
  @SdkSuppress(minSdkVersion = 21)
  public void testSetFitSystemWindows() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;
    final View view = new View(col.getContext());

    // Create a mock which calls the default impl of onApplyWindowInsets()
    final CoordinatorLayout.Behavior<View> mockBehavior = mock(CoordinatorLayout.Behavior.class);
    doCallRealMethod()
        .when(mockBehavior)
        .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));

    // Assert that the CoL is currently not set to fitSystemWindows
    assertFalse(col.getFitsSystemWindows());

    // Now add a view with our mocked behavior to the CoordinatorLayout
    view.setFitsSystemWindows(true);
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
            lp.setBehavior(mockBehavior);
            col.addView(view, lp);
          }
        });
    getInstrumentation().waitForIdleSync();

    // Now request some insets and wait for the pass to happen
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.requestApplyInsets();
          }
        });
    getInstrumentation().waitForIdleSync();

    // Verify that onApplyWindowInsets() has not been called
    verify(mockBehavior, never())
        .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));

    // Now enable fits system windows and wait for a pass to happen
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.setFitsSystemWindows(true);
          }
        });
    getInstrumentation().waitForIdleSync();

    // Verify that onApplyWindowInsets() has been called with some insets
    verify(mockBehavior, atLeastOnce())
        .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
  }

  @Test
  public void testLayoutChildren() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;
    final View view = new View(col.getContext());
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(view, 100, 100);
          }
        });
    getInstrumentation().waitForIdleSync();
    int horizontallyCentered = (col.getWidth() - view.getWidth()) / 2;
    int end = col.getWidth() - view.getWidth();
    int verticallyCentered = (col.getHeight() - view.getHeight()) / 2;
    int bottom = col.getHeight() - view.getHeight();
    final int[][] testCases = {
      // gravity, expected left, expected top
      {Gravity.NO_GRAVITY, 0, 0},
      {Gravity.LEFT, 0, 0},
      {GravityCompat.START, 0, 0},
      {Gravity.TOP, 0, 0},
      {Gravity.CENTER, horizontallyCentered, verticallyCentered},
      {Gravity.CENTER_HORIZONTAL, horizontallyCentered, 0},
      {Gravity.CENTER_VERTICAL, 0, verticallyCentered},
      {Gravity.RIGHT, end, 0},
      {GravityCompat.END, end, 0},
      {Gravity.BOTTOM, 0, bottom},
      {Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, horizontallyCentered, bottom},
      {Gravity.RIGHT | Gravity.CENTER_VERTICAL, end, verticallyCentered},
    };
    for (final int[] testCase : testCases) {
      activityTestRule.runOnUiThread(
          new Runnable() {
            @Override
            public void run() {
              final CoordinatorLayout.LayoutParams lp =
                  (CoordinatorLayout.LayoutParams) view.getLayoutParams();
              lp.gravity = testCase[0];
              view.setLayoutParams(lp);
            }
          });
      getInstrumentation().waitForIdleSync();
      activityTestRule.runOnUiThread(
          new Runnable() {
            @Override
            public void run() {
              assertThat("Gravity: " + testCase[0], view.getLeft(), is(testCase[1]));
              assertThat("Gravity: " + testCase[0], view.getTop(), is(testCase[2]));
            }
          });
    }
  }

  @Test
  public void testInsetDependency() {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    final CoordinatorLayout.LayoutParams lpInsetLeft = col.generateDefaultLayoutParams();
    lpInsetLeft.insetEdge = Gravity.LEFT;

    final CoordinatorLayout.LayoutParams lpInsetRight = col.generateDefaultLayoutParams();
    lpInsetRight.insetEdge = Gravity.RIGHT;

    final CoordinatorLayout.LayoutParams lpInsetTop = col.generateDefaultLayoutParams();
    lpInsetTop.insetEdge = Gravity.TOP;

    final CoordinatorLayout.LayoutParams lpInsetBottom = col.generateDefaultLayoutParams();
    lpInsetBottom.insetEdge = Gravity.BOTTOM;

    final CoordinatorLayout.LayoutParams lpDodgeLeft = col.generateDefaultLayoutParams();
    lpDodgeLeft.dodgeInsetEdges = Gravity.LEFT;

    final CoordinatorLayout.LayoutParams lpDodgeLeftAndTop = col.generateDefaultLayoutParams();
    lpDodgeLeftAndTop.dodgeInsetEdges = Gravity.LEFT | Gravity.TOP;

    final CoordinatorLayout.LayoutParams lpDodgeAll = col.generateDefaultLayoutParams();
    lpDodgeAll.dodgeInsetEdges = Gravity.FILL;

    final View a = new View(col.getContext());
    final View b = new View(col.getContext());

    assertThat(dependsOn(lpDodgeLeft, lpInsetLeft, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeLeft, lpInsetRight, col, a, b), is(false));
    assertThat(dependsOn(lpDodgeLeft, lpInsetTop, col, a, b), is(false));
    assertThat(dependsOn(lpDodgeLeft, lpInsetBottom, col, a, b), is(false));

    assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetLeft, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetRight, col, a, b), is(false));
    assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetTop, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetBottom, col, a, b), is(false));

    assertThat(dependsOn(lpDodgeAll, lpInsetLeft, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeAll, lpInsetRight, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeAll, lpInsetTop, col, a, b), is(true));
    assertThat(dependsOn(lpDodgeAll, lpInsetBottom, col, a, b), is(true));

    assertThat(dependsOn(lpInsetLeft, lpDodgeLeft, col, a, b), is(false));
  }

  private static boolean dependsOn(
      CoordinatorLayout.LayoutParams lpChild,
      CoordinatorLayout.LayoutParams lpDependency,
      CoordinatorLayout col,
      View child,
      View dependency) {
    child.setLayoutParams(lpChild);
    dependency.setLayoutParams(lpDependency);
    return lpChild.dependsOn(col, child, dependency);
  }

  @Test
  public void testInsetEdge() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    final View insetView = new View(col.getContext());
    final View dodgeInsetView = new View(col.getContext());
    final AtomicInteger originalTop = new AtomicInteger();

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            CoordinatorLayout.LayoutParams lpInsetView = col.generateDefaultLayoutParams();
            lpInsetView.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
            lpInsetView.height = 100;
            lpInsetView.gravity = Gravity.TOP | Gravity.LEFT;
            lpInsetView.insetEdge = Gravity.TOP;
            col.addView(insetView, lpInsetView);
            insetView.setBackgroundColor(0xFF0000FF);

            CoordinatorLayout.LayoutParams lpDodgeInsetView = col.generateDefaultLayoutParams();
            lpDodgeInsetView.width = 100;
            lpDodgeInsetView.height = 100;
            lpDodgeInsetView.gravity = Gravity.TOP | Gravity.LEFT;
            lpDodgeInsetView.dodgeInsetEdges = Gravity.TOP;
            col.addView(dodgeInsetView, lpDodgeInsetView);
            dodgeInsetView.setBackgroundColor(0xFFFF0000);
          }
        });
    getInstrumentation().waitForIdleSync();
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            List<View> dependencies = col.getDependencies(dodgeInsetView);
            assertThat(dependencies.size(), is(1));
            assertThat(dependencies.get(0), is(insetView));

            // Move the insetting view
            originalTop.set(dodgeInsetView.getTop());
            assertThat(originalTop.get(), is(insetView.getBottom()));
            ViewCompat.offsetTopAndBottom(insetView, 123);
          }
        });
    getInstrumentation().waitForIdleSync();
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            // Confirm that the dodging view was moved by the same size
            assertThat(dodgeInsetView.getTop() - originalTop.get(), is(123));
          }
        });
  }

  @Test
  public void testDependentViewChanged() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    // Add two views, A & B, where B depends on A
    final View viewA = new View(col.getContext());
    final CoordinatorLayout.LayoutParams lpA = col.generateDefaultLayoutParams();
    lpA.width = 100;
    lpA.height = 100;

    final View viewB = new View(col.getContext());
    final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
    lpB.width = 100;
    lpB.height = 100;
    final CoordinatorLayout.Behavior behavior = spy(new DependentBehavior(viewA));
    lpB.setBehavior(behavior);

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(viewA, lpA);
            col.addView(viewB, lpB);
          }
        });
    getInstrumentation().waitForIdleSync();

    // Reset the Behavior since onDependentViewChanged may have already been called as part of
    // any layout/draw passes already
    reset(behavior);

    // Now offset view A
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            ViewCompat.offsetLeftAndRight(viewA, 20);
            ViewCompat.offsetTopAndBottom(viewA, 20);
          }
        });
    getInstrumentation().waitForIdleSync();

    // And assert that view B's Behavior was called appropriately
    verify(behavior, times(1)).onDependentViewChanged(col, viewB, viewA);
  }

  @Test
  public void testDependentViewRemoved() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    // Add two views, A & B, where B depends on A
    final View viewA = new View(col.getContext());
    final View viewB = new View(col.getContext());
    final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
    final CoordinatorLayout.Behavior behavior = spy(new DependentBehavior(viewA));
    lpB.setBehavior(behavior);

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(viewA);
            col.addView(viewB, lpB);
          }
        });
    getInstrumentation().waitForIdleSync();

    // Now remove view A
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.removeView(viewA);
          }
        });

    // And assert that View B's Behavior was called appropriately
    verify(behavior, times(1)).onDependentViewRemoved(col, viewB, viewA);
  }

  @Test
  public void testGetDependenciesAfterDependentViewRemoved() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    // Add two views, A & B, where B depends on A
    final View viewA = new View(col.getContext());
    final View viewB = new View(col.getContext());
    final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
    final CoordinatorLayout.Behavior behavior =
        new DependentBehavior(viewA) {
          @Override
          public void onDependentViewRemoved(
              CoordinatorLayout parent, View child, View dependency) {
            parent.getDependencies(child);
          }
        };
    lpB.setBehavior(behavior);

    // Now add views
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(viewA);
            col.addView(viewB, lpB);
          }
        });

    // Wait for a layout
    getInstrumentation().waitForIdleSync();

    // Now remove view A, which will trigger onDependentViewRemoved() on view B's behavior
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.removeView(viewA);
          }
        });
  }

  @Test
  public void testDodgeInsetBeforeLayout() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    // Add a dummy view, which will be used to trigger a hierarchy change.
    final View dummy = new View(col.getContext());

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(dummy);
          }
        });

    // Wait for a layout.
    getInstrumentation().waitForIdleSync();

    final View dodge = new View(col.getContext());
    final CoordinatorLayout.LayoutParams lpDodge = col.generateDefaultLayoutParams();
    lpDodge.dodgeInsetEdges = Gravity.BOTTOM;
    lpDodge.setBehavior(
        new Behavior() {
          @Override
          public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
            // Any non-empty rect is fine here.
            rect.set(0, 0, 10, 10);
            return true;
          }
        });

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(dodge, lpDodge);

            // Ensure the new view is in the list of children.
            int heightSpec = MeasureSpec.makeMeasureSpec(col.getHeight(), MeasureSpec.EXACTLY);
            int widthSpec = MeasureSpec.makeMeasureSpec(col.getWidth(), MeasureSpec.EXACTLY);
            col.measure(widthSpec, heightSpec);

            // Force a hierarchy change.
            col.removeView(dummy);
          }
        });

    // Wait for a layout.
    getInstrumentation().waitForIdleSync();
  }

  @Test
  public void testGoneViewsNotMeasuredLaidOut() throws Throwable {
    final CoordinatorLayoutActivity activity = activityTestRule.getActivity();
    final CoordinatorLayout col = activity.mCoordinatorLayout;

    // Now create a GONE view and add it to the CoordinatorLayout
    final View imageView = new View(activity);
    imageView.setVisibility(View.GONE);
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            col.addView(imageView, 200, 200);
          }
        });
    // Wait for a layout and measure pass
    getInstrumentation().waitForIdleSync();

    // And assert that it has not been laid out
    assertFalse(imageView.getMeasuredWidth() > 0);
    assertFalse(imageView.getMeasuredHeight() > 0);
    assertFalse(ViewCompat.isLaidOut(imageView));

    // Now set the view to INVISIBLE
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            imageView.setVisibility(View.INVISIBLE);
          }
        });
    // Wait for a layout and measure pass
    getInstrumentation().waitForIdleSync();

    // And assert that it has been laid out
    assertTrue(imageView.getMeasuredWidth() > 0);
    assertTrue(imageView.getMeasuredHeight() > 0);
    assertTrue(ViewCompat.isLaidOut(imageView));
  }

  @Test
  public void testNestedScrollingDispatchesToBehavior() throws Throwable {
    final CoordinatorLayoutActivity activity = activityTestRule.getActivity();
    final CoordinatorLayout col = activity.mCoordinatorLayout;

    // Now create a view and add it to the CoordinatorLayout with the spy behavior,
    // along with a NestedScrollView
    final ImageView imageView = new ImageView(activity);
    final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);

            CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
            clp.setBehavior(behavior);
            col.addView(imageView, clp);
          }
        });

    // Now vertically swipe up on the NSV, causing nested scrolling to occur
    onView(withId(R.id.nested_scrollview)).perform(swipeUp());

    // Verify that the Behavior's onStartNestedScroll was called once
    verify(behavior, times(1))
        .onStartNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(View.class), // direct child target
            any(int.class)); // axes

    // Verify that the Behavior's onNestedScrollAccepted was called once
    verify(behavior, times(1))
        .onNestedScrollAccepted(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(View.class), // direct child target
            any(int.class)); // axes

    // Verify that the Behavior's onNestedPreScroll was called at least once
    verify(behavior, atLeastOnce())
        .onNestedPreScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(int.class), // dx
            any(int.class), // dy
            any(int[].class)); // consumed

    // Verify that the Behavior's onNestedScroll was called at least once
    verify(behavior, atLeastOnce())
        .onNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(int.class), // dx consumed
            any(int.class), // dy consumed
            any(int.class), // dx unconsumed
            any(int.class)); // dy unconsumed

    // Verify that the Behavior's onStopNestedScroll was called once
    verify(behavior, times(1))
        .onStopNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class)); // target
  }

  @Test
  public void testNestedScrollingDispatchingToBehaviorWithGoneView() throws Throwable {
    final CoordinatorLayoutActivity activity = activityTestRule.getActivity();
    final CoordinatorLayout col = activity.mCoordinatorLayout;

    // Now create a GONE view and add it to the CoordinatorLayout with the spy behavior,
    // along with a NestedScrollView
    final ImageView imageView = new ImageView(activity);
    imageView.setVisibility(View.GONE);
    final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);

            CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
            clp.setBehavior(behavior);
            col.addView(imageView, clp);
          }
        });

    // Now vertically swipe up on the NSV, causing nested scrolling to occur
    onView(withId(R.id.nested_scrollview)).perform(swipeUp());

    // Verify that the Behavior's onStartNestedScroll was not called
    verify(behavior, never())
        .onStartNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(View.class), // direct child target
            any(int.class)); // axes

    // Verify that the Behavior's onNestedScrollAccepted was not called
    verify(behavior, never())
        .onNestedScrollAccepted(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(View.class), // direct child target
            any(int.class)); // axes

    // Verify that the Behavior's onNestedPreScroll was not called
    verify(behavior, never())
        .onNestedPreScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(int.class), // dx
            any(int.class), // dy
            any(int[].class)); // consumed

    // Verify that the Behavior's onNestedScroll was not called
    verify(behavior, never())
        .onNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class), // target
            any(int.class), // dx consumed
            any(int.class), // dy consumed
            any(int.class), // dx unconsumed
            any(int.class)); // dy unconsumed

    // Verify that the Behavior's onStopNestedScroll was not called
    verify(behavior, never())
        .onStopNestedScroll(
            eq(col), // parent
            eq(imageView), // child
            any(View.class)); // target
  }

  @Test
  public void testNestedScrollingTriggeringDependentViewChanged() throws Throwable {
    final CoordinatorLayoutActivity activity = activityTestRule.getActivity();
    final CoordinatorLayout col = activity.mCoordinatorLayout;

    // First a NestedScrollView to trigger nested scrolling
    final View scrollView =
        LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, false);

    // Now create a View and Behavior which depend on the scrollview
    final ImageView dependentView = new ImageView(activity);
    final CoordinatorLayout.Behavior dependentBehavior = spy(new DependentBehavior(scrollView));

    // Finally a view which accepts nested scrolling in the CoordinatorLayout
    final ImageView nestedScrollAwareView = new ImageView(activity);

    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            // First add the ScrollView
            col.addView(scrollView);

            // Now add the view which depends on the scrollview
            CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
            clp.setBehavior(dependentBehavior);
            col.addView(dependentView, clp);

            // Now add the nested scrolling aware view
            clp = new CoordinatorLayout.LayoutParams(200, 200);
            clp.setBehavior(new NestedScrollingBehavior());
            col.addView(nestedScrollAwareView, clp);
          }
        });

    // Wait for any layouts, and reset the Behavior so that the call counts are 0
    getInstrumentation().waitForIdleSync();
    reset(dependentBehavior);

    // Now vertically swipe up on the NSV, causing nested scrolling to occur
    onView(withId(R.id.nested_scrollview)).perform(swipeUp());

    // Verify that the Behavior's onDependentViewChanged is not called due to the
    // nested scroll
    verify(dependentBehavior, never())
        .onDependentViewChanged(
            eq(col), // parent
            eq(dependentView), // child
            eq(scrollView)); // axes
  }

  @Test
  public void testDodgeInsetViewWithEmptyBounds() throws Throwable {
    final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout;

    // Add a view with zero height/width which is set to dodge its bounds
    final View view = new View(col.getContext());
    final Behavior spyBehavior = spy(new DodgeBoundsBehavior());
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
            lp.dodgeInsetEdges = Gravity.BOTTOM;
            lp.gravity = Gravity.BOTTOM;
            lp.height = 0;
            lp.width = 0;
            lp.setBehavior(spyBehavior);
            col.addView(view, lp);
          }
        });

    // Wait for a layout
    getInstrumentation().waitForIdleSync();

    // Now add an non-empty bounds inset view to the bottom of the CoordinatorLayout
    activityTestRule.runOnUiThread(
        new Runnable() {
          @Override
          public void run() {
            final View dodge = new View(col.getContext());
            final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
            lp.insetEdge = Gravity.BOTTOM;
            lp.gravity = Gravity.BOTTOM;
            lp.height = 60;
            lp.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
            col.addView(dodge, lp);
          }
        });

    // Verify that the Behavior of the view with empty bounds does not have its
    // getInsetDodgeRect() called
    verify(spyBehavior, never()).getInsetDodgeRect(same(col), same(view), any(Rect.class));
  }

  public static class NestedScrollingBehavior extends CoordinatorLayout.Behavior<ImageView> {
    @Override
    public boolean onStartNestedScroll(
        CoordinatorLayout coordinatorLayout,
        ImageView child,
        View directTargetChild,
        View target,
        int nestedScrollAxes) {
      // Return true so that we always accept nested scroll events
      return true;
    }
  }

  public static class DodgeBoundsBehavior extends Behavior<View> {
    @Override
    public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
      rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
      return true;
    }
  }
}
